<?php
/**
 * Smithy PHP library
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * Smithy is a free Zend Framework based PHP library.
 *
 * @category   Smithy
 * @package    Smithy_Model
 * @subpackage Gateway
 * @copyright  Copyright (c) 2008-2009 Kovács Tamás
 * @license    New BSD License
 * @version    $Id: Abstract.php ktamas $
 */


/**
 * A basic model gateway class.
 *
 * Defines some common model related functions.
 *
 * @author Kovács Tamás
 * @category Smithy
 * @package Smithy_Model
 * @subpackage Gateway
 * @version 2.0
 */
abstract class Smithy_Model_Gateway_Abstract
{
    const GATEWAY = 'Gateway';
    const COLLECTION = 'Collection';
    const ENTITY = 'Entity';
    const TABLE = 'Doctrine';
    const FORM = 'Form';

    /**
     * @var array Zend_Form instances
     */
    protected static $_forms = array();

    /**
     * @var array Smithy_Model_Gateway_Abstract instances
     */
    protected static $_gateways = array();

    /**
     * Default module name
     *
     * @var string|null
     */
    protected $_moduleName = null;

    /**
     * The name of the model, the gateway is for.
     *
     * @var string|null
     */
    protected $_modelName = null;

    /**
     * Primary table for the model.
     *
     * @var string|null
     */
    protected $_tableName = null;

    /**
     * Primary gateway name for the model.
     *
     * @var string|null
     */
    protected $_gatewayName = null;

    /**
     * Primary form name for the model.
     *
     * @var string|null
     */
    protected $_formName = null;

    /**
     * The default entity name to be instantiated.
     *
     * @var string|null
     */
    protected $_entityName = null;

    /**
     * The default collection name to be instantiated.
     *
     * @var string|null
     */
    protected $_collectionName = null;

    /**
     * The prefix for the table used in the basic dql statements.
     *
     * @var string
     */
    protected $_modelDqlPrefix = null;

    /**
     * Provides interface for findBy* functions.
     *
     * @var array
     */
    protected $_magicFinders = array(
        'Id' => array('id')
    );

    /**
     * Provides interface for fetchBy* functions.
     *
     * @var array
     */
    protected $_magicFetchers = array(
        'Id' => array('id')
    );


    /**
     * Constructor
     *
     * @return void
     */
    public function __construct()
    {
        $this->init();
    }

    /**
     * Initialize gateway.
     *
     * @return void
     */
    public function init()
    {}

    /**
     * Returns with the models main Doctrine_Query.
     *
     * @return Doctrine_Query
     */
    protected function _dql()
    {
        return $this->getTable()
           ->createQuery($this->_modelDqlPrefix);
    }

    /**
     * Returns with the models main select Doctrine_Query.
     *
     * @return Doctrine_Query
     */
    protected function _dqlSelect()
    {
        return $this->_dql()->select($this->_modelDqlPrefix . '*');
    }

    /**
     * Creates an instance from the gateway's default entity (or from the given entity).
     *
     * @param array|Doctrine_Record|null $data Data for the entity object. [OPTIONAL]
     * @param string $entityName [OPTIONAL]
     * @param string $entityClass If not null the $entityName parameter will be ignored. [OPTIONAL]
     * @param string|null $moduleName [OPTIONAL]
     * @return Smithy_Model_Entity_Abstract
     * @throws Smithy_Model_Gateway_Exception
     */
    public function createEntity($data = null, $entityName = null, $entityClass = null, $moduleName = null)
    {
        if (false === $data) {
            return false;
        }

        if (null === $entityClass) {
            $entityName = null === $entityName ? (null === $this->_entityName ? $this->_modelName : $this->_entityName) : $entityName;
            $entityClass = $this->_getModelClassName($entityName, Smithy_Model_Gateway_Abstract::ENTITY, $moduleName);
        }

        if (null === $entityClass) {
            throw new Smithy_Model_Gateway_Exception('The enitiy class is not defined. ');
        }

        $moduleName = null === $moduleName ? (null === $this->_moduleName ? '' : $this->_moduleName) : ucfirst(strtolower($moduleName));
        Smithy_Loader::loadResource($entityName, Smithy_Model_Gateway_Abstract::ENTITY, $moduleName);

        return new $entityClass($data, $this);
    }

    /**
     * Creates an instance from the gateway's default collection (or from the given collection).
     *
     * @param array|arrayAccess|IteratorAggregate|null $data Data for the entity object. [OPTIONAL]
     * @param string $collectionName [OPTIONAL]
     * @param string $collectionClass If not null the $collectionName parameter will be ignored. [OPTIONAL]
     * @param string|null $moduleName [OPTIONAL]
     * @return Smithy_Model_Collection_Abstract
     */
    public function createCollection($collection = null, $collectionName = null, $collectionClass = null, $moduleName = null)
    {
        if (null === $collectionClass) {
            $collectionName = null === $collectionName ? (null === $this->_collectionName ? $this->_modelName : $this->_collectionName) : ucfirst(strtolower($collectionName));
            $collectionClass = $this->_getModelClassName($collectionName, Smithy_Model_Gateway_Abstract::COLLECTION, $moduleName);
        }

        if (null === $collectionClass) {
            throw new Smithy_Model_Gateway_Exception('The collection class is not defined. ');
        }

        $moduleName = null === $moduleName ? (null === $this->_moduleName ? '' : $this->_moduleName) : ucfirst(strtolower($moduleName));
        Smithy_Loader::loadResource($collectionName, Smithy_Model_Gateway_Abstract::COLLECTION, $moduleName);

        return new $collectionClass($collection, $this);
    }

    /**
     * Get table class
     *
     * @param string|null $tableName [OPTIONAL]
     * @param string|null $tableClass [OPTIONAL]
     * @param string|null $moduleName [OPTIONAL]
     * @return Doctrine_Table
     * @throws Smithy_Model_Gateway_Exception
     * @throws Doctrine_Exception
     */
    public function getTable($tableName = null, $tableClass = null, $moduleName = null)
    {
        if (null === $tableClass) {
            $tableName = null === $tableName ? (null === $this->_tableName ? $this->_modelName : $this->_tableName) : ucfirst(strtolower($tableName));
            $tableClass = $this->_getModelClassName($tableName, Smithy_Model_Gateway_Abstract::TABLE, $moduleName);
        }

        if (null === $tableClass) {
            throw new Smithy_Model_Gateway_Exception('No table name was specified for retrieving table. ');
        }

        return Doctrine::getTable($tableClass);
    }

    /**
     * Get form instace
     *
     * @param string $formName [OPTIONAL]
     * @param string $fromClass If not null the $formName parameter will be ignored. [OPTIONAL]
     * @param string|null $moduleName [OPTIONAL]
     * @return Smithy_Form
     * @throws Smithy_Model_Gateway_Exception
     */
    public function getForm($formName = null, $formClass = null, $moduleName = null)
    {
        if (null === $formClass) {
            $formName = null === $formName ? (null === $this->_formName ? $this->_modelName : $this->_formName) : ucfirst(strtolower($formName));
            $formClass = $this->_getModelClassName($formName, Smithy_Model_Gateway_Abstract::FORM, $moduleName);
        }

        if (null === $formClass) {
            throw new Smithy_Model_Gateway_Exception('The form class is not defined. ');
        }

        if (!array_key_exists($formClass, self::$_forms)) {
            $moduleName = null === $moduleName ? (null === $this->_moduleName ? '' : $this->_moduleName) : ucfirst(strtolower($moduleName));
            $class = Smithy_Loader::loadResource($formName, Smithy_Model_Gateway_Abstract::FORM, $moduleName);
            self::$_forms[$formClass] = new $class;
        }

        return self::$_forms[$formClass];
    }

    /**
     * Get model gateway instace
     *
     * @param string $gatewayName [OPTIONAL]
     * @param string $gatewayClass If not null the $gatewayName parameter will be ignored. [OPTIONAL]
     * @param string|null $moduleName [OPTIONAL]
     * @return Smithy_Model_Gateway_Abstract
     * @throws Smithy_Model_Gateway_Exception
     */
    public function getGateway($gatewayName = null, $gatewayClass = null, $moduleName=null)
    {
        if (null === $gatewayClass) {
            $gatewayName = null === $gatewayName ? (null === $this->_gatewayName ? $this->_modelName : $this->_gatewayName) : $gatewayName;
            $moduleName = null === $moduleName ? (null === $this->_moduleName ? '' : $this->_moduleName) : ucfirst($moduleName);
            $gatewayClass = '' === $moduleName ? '' : $moduleName . '_';
            $gatewayClass .= $gatewayName . Smithy_Model_Gateway_Abstract::GATEWAY;
        }

        if (null === $gatewayClass) {
            throw new Smithy_Model_Gateway_Exception('The gateway class is not defined. ');
        }

        if (!array_key_exists($gatewayClass, self::$_gateways)) {
            self::$_gateways[$gatewayClass] = new $gatewayClass;
        }

        return self::$_gateways[$gatewayClass];
    }

    /**
     * Enter description here...
     *
     * @param string $modelName
     * @param string $type
     * @param string $moduleName [OPTIONAL]
     * @return string
     */
    protected function _getModelClassName($modelName, $type, $moduleName = null)
    {
        $moduleName = null === $moduleName ? (null === $this->_moduleName ? '' : $this->_moduleName) : ucfirst($moduleName);
        return $moduleName . '_' . $type . '_' . $modelName;
    }

    /**
     * Fetches all entities as a collection.
     *
     * @return Smithy_Model_Collection_Abstract
     */
    public function fetchAll()
    {
        return $this->createCollection(
            $this->_dqlSelect()->execute()
        );
    }

    /**
     * Enter description here...
     *
     * @param unknown_type $name
     * @param unknown_type $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        try {
            return $this->_magicFinder($name, $arguments);
        } catch (Smithy_Model_Gateway_Exception $e) {}

        try {
            return $this->_magicFetcher($name, $arguments);
        } catch (Smithy_Model_Gateway_Exception $e) {}

        throw new Smithy_Model_Gateway_Exception("Function '{$name}' does not exist. ");
    }

    /**
     * Enter description here...
     *
     * @param string $name
     * @param string $arguments
     * @return Smithy_Model_Entity_Abstract
     * @throws Smithy_Model_Gateway_Exception
     */
    protected function _magicFinder($name, $arguments)
    {
        if (!preg_match('%findBy([a-zA-Z])%', $name)) {
            throw new Smithy_Model_Gateway_Exception('Function is not a valid finder method. ');
        }
        $finderKey = substr($name, 6);

        if (!array_key_exists($finderKey, $this->_magicFinders)) {
            throw new Smithy_Model_Gateway_Exception("Magic finder '{$finderKey}' does not exist. ");
        }

        $finderFields = $this->_magicFinders[$finderKey];
        if (!is_array($finderFields)) {
            $finderFields = array($finderFields);
        }

        $dql = $this->_dqlSelect();

        foreach ($finderFields as $key => $finderField) {
            $dql->andWhere(
                $this->_modelDqlPrefix . '.' . $finderField . '=?',
                $arguments[$key]
            );
        }

        return $this->createEntity(
            $dql->fetchOne()
        );
    }

    /**
     * Enter description here...
     *
     * @param string $name
     * @param string $arguments
     * @return Smithy_Model_Entity_Abstract
     * @throws Smithy_Model_Gateway_Exception
     */
    protected function _magicFetcher($name, $arguments)
    {
        if (!preg_match('%fecthBy([a-zA-Z])%', $name)) {
            throw new Smithy_Model_Gateway_Exception('Function is not a valid fetcher method. ');
        }
        $fetcherKey = substr($name, 7);

        if (!array_key_exists($fetcherKey, $this->_magicFetchers)) {
            throw new Smithy_Model_Gateway_Exception("Magic fetcher '{$fetcherKey}' does not exist. ");
        }

        $fetcherFields = $this->_magicFetchers[$fetcherKey];
        if (!is_array($fetcherFields)) {
            $fetcherFields = array($fetcherFields);
        }

        $dql = $this->_dqlSelect();

        foreach ($fetcherFields as $key => $fetcherField) {
            $dql->andWhere(
                $this->_modelDqlPrefix . '.' . $fetcherField . '=?',
                $arguments[$key]
            );
        }

        return $this->createCollection(
            $dql->execute()
        );
    }

}
